#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import re
import json

from common.common import check_command_injection
from db2.backup.util.ha_utils import is_power_rhel_ha
from db2.comm.const import Db2Const
from db2.comm.error_code import DB2Code, Db2ErrCode
from db2.comm.db2_cmd import get_db2_instance, check_os_user, get_operating_system, check_cluster_node, \
    check_cluster_node_right, check_user_password
from db2.comm.util.os_utils import check_ha_cluster_node_right
from common.logger import Logger
from db2.comm.constant import DB2Type
from db2.comm.param_parse import Param
from db2.comm.util.common_util import Db2CommonUtil
from db2.comm.db2_exception import ErrCodeException

LOGGER = Logger().get_logger(filename="db2.log")


class Instance:
    def __init__(self, pid, params):
        self.pid = pid
        self.context = params
        self.instance = self.context.application
        self.name = self.instance.get("name")
        self.type = self.instance.get("subType")
        self.is_top_instance = self.context.app_extend_info.get("isTopInstance")
        self.cluster_type = self.context.app_extend_info.get("clusterType")
        self.node_number = self.context.app_extend_info.get("nodeNums")
        self.username = self.context.app_auth_key
        self.password = self.context.app_auth_pwd
        self.node_ip = self.context.env_extend_info.get("allNodes", '')
        if self.node_ip:
            if check_command_injection(self.node_ip):
                raise Exception("Param of node_ip invalid")
        LOGGER.info(f"check instance")

    def check_db2_message(self, resource_type):
        LOGGER.info(f"Begin to get db2 instance from  pid :{self.pid}")
        ret, params = self._check_db2_user_message()
        if not ret:
            return params
        if is_power_rhel_ha(self.cluster_type) and resource_type == DB2Type.DB2INSTANCE:
            ret, params = self._check_db2_ha_cluster_message()
            if not ret:
                return params
        if resource_type == DB2Type.DB2CLUSTERINSTANCE:
            if self.cluster_type == Db2Const.DPF_CLUSTER_TYPE:
                ret, params = self._check_db2_cluster_message()
            if not ret:
                return params
        ret = Db2CommonUtil.check_process_service(self.username)
        if not ret:
            error_message = "Process is not start!"
            params = Param.get_body_error_param(DB2Code.FAILED.value, Db2ErrCode.DB_SERVICE_ERROR,
                                                error_message)
            LOGGER.error(error_message)
            return params
        ret, instance_name = get_db2_instance(self.username)
        instance = self._parse_instance(instance_name)
        if not ret or self.name != instance:
            error_message = "Instance is not exist!"
            params = Param.get_body_error_param(DB2Code.FAILED.value, Db2ErrCode.DB_USER_NOT_EXSIT,
                                                error_message)
            LOGGER.error(error_message)
            return params
        version = Db2CommonUtil.get_version(self.username)
        if not version:
            error_message = "Query version failed !"
            params = Param.get_body_error_param(DB2Code.FAILED.value, Db2ErrCode.FAILED_EXECUTE_COMMAND,
                                                error_message)
            LOGGER.error(error_message)
            return params
        ret, deploy_operating_system = get_operating_system(self.username)
        LOGGER.info(f"Deploy operating system is {deploy_operating_system}")
        message = {}
        message["role"] = "1"
        message["version"] = version
        message["deployOperatingSystem"] = deploy_operating_system[0]
        message["databaseBits"] = deploy_operating_system[1][0]
        try:
            success_message = json.dumps(message)
        except Exception as ex:
            params = Param.get_body_error_param(DB2Code.FAILED.value, Db2ErrCode.FAILED_EXECUTE_COMMAND, str(ex))
            return params
        params = Param.get_body_error_param(DB2Code.SUCCESS.value, DB2Code.SUCCESS.value, success_message)
        return params

    def _check_db2_cluster_message(self):
        """PDF集群节点判断信息"""
        LOGGER.info("Begin to check cluster instance")
        ret, cluster_nodes = check_cluster_node(self.username)
        if not ret:
            error_message = "Check nodes failed!"
            params = Param.get_body_error_param(DB2Code.FAILED.value, Db2ErrCode.ERR_DATABASE_STATUS,
                                                error_message)
            LOGGER.error(error_message)
            return False, params
        nodes = self._parse_db2_cluster_nodes(cluster_nodes)
        if len(nodes) == 1:
            error_message = "Check nodes failed!"
            params = Param.get_body_error_param(DB2Code.FAILED.value, Db2ErrCode.CLUSTER_TYPE_ERROR,
                                                error_message)
            LOGGER.error(error_message)
            return False, params
        if len(nodes) != int(self.node_number):
            error_message = "Check nodes failed!"
            params = Param.get_body_error_param(DB2Code.FAILED.value, Db2ErrCode.NOT_INCLUDE_ALL_CLUSTER_INSTANCES,
                                                error_message)
            LOGGER.error(error_message)
            return False, params
        ret, hosts_node = check_cluster_node_right()
        if not ret:
            error_message = "Select hosts failed!"
            params = Param.get_body_error_param(DB2Code.FAILED.value, Db2ErrCode.ERR_DATABASE_STATUS,
                                                error_message)
            LOGGER.error(error_message)
            return False, params
        host_node = self._parse_hosts_cluster_nodes(hosts_node)
        for node in nodes:
            if host_node.get(node, "") not in self.node_ip:
                error_message = "Not find node_ip!"
                params = Param.get_body_error_param(DB2Code.FAILED.value, Db2ErrCode.INSTANCES_NOT_BELONG_SAME_CLUSTER,
                                                    error_message)
                LOGGER.error(error_message)
                return False, params
        return True, {}

    def _check_db2_ha_cluster_message(self):
        LOGGER.info("Begin to check ha cluster instance")
        message = {}
        try:
            ret, params = check_ha_cluster_node_right()
        except Exception:
            error_message = "It is not ha_cluster_node"
            params = Param.get_body_error_param(DB2Code.FAILED.value, Db2ErrCode.CLUSTER_TYPE_ERROR,
                                                error_message)
            LOGGER.error(error_message)
            return False, params
        if not ret:
            error_message = "Select cluster failed!"
            params = Param.get_body_error_param(DB2Code.FAILED.value, Db2ErrCode.INSTANCES_NOT_BELONG_SAME_CLUSTER,
                                                error_message)
            LOGGER.error(error_message)
            return False, params
        try:
            ret, params = check_cluster_node(self.name)
        except ErrCodeException as ex:
            error_message = "User is invalid"
            params = Param.get_body_error_param(DB2Code.FAILED.value, Db2ErrCode.DB_USER_NOT_EXSIT, error_message)
            LOGGER.error(error_message)
            return False, params
        if not ret:
            message["role"] = "2"
            success_message = json.dumps(message)
            params = Param.get_body_error_param(DB2Code.SUCCESS.value, DB2Code.SUCCESS.value,
                                                success_message)
            return False, params
        message["role"] = "1"
        return True, params

    def _check_db2_user_message(self):
        LOGGER.info("Begin to check db2 user!")
        params = {}
        ret = check_os_user(self.username)
        if not ret:
            error_message = "User is not exist!"
            params = Param.get_body_error_param(DB2Code.FAILED.value, Db2ErrCode.DB_INVALID_PASSWORD,
                                                error_message)
            LOGGER.error(error_message)
            return False, params
        ret, login_check = check_user_password(self.username, self.password)
        if not ret:
            error_message = "Login failed!"
            params = Param.get_body_error_param(DB2Code.FAILED.value, Db2ErrCode.DB_INVALID_PASSWORD,
                                                error_message)
            LOGGER.error(error_message)
            return False, params
        if int(self.is_top_instance) == 1 or self.cluster_type == Db2Const.HADR:
            LOGGER.info("Begin to check single instance!")
            ret, cluster_nodes = check_cluster_node(self.username)
            if not ret:
                error_message = "Check nodes failed!"
                params = Param.get_body_error_param(DB2Code.FAILED.value, Db2ErrCode.DB_USER_NOT_EXSIT,
                                                    error_message)
                LOGGER.error(error_message)
                return False, params
            nodes = self._parse_db2_cluster_nodes(cluster_nodes)
            if len(nodes) > 1:
                error_message = "This is dpf_cluster!"
                params = Param.get_body_error_param(DB2Code.FAILED.value, Db2ErrCode.CLUSTER_TYPE_ERROR,
                                                    error_message)
                LOGGER.error(error_message)
                return False, params
            try:
                ret, node = check_ha_cluster_node_right()
            except Exception:
                return True, params
            if ret:
                error_message = "This is ha_cluster!"
                params = Param.get_body_error_param(DB2Code.FAILED.value, Db2ErrCode.CLUSTER_TYPE_ERROR,
                                                    error_message)
                LOGGER.error(error_message)
                return False, params
        return True, params

    def _parse_db2_cluster_nodes(self, cluster_nodes):
        LOGGER.info("Begin to parse cluster_nodes")
        nodes = []
        cluster = cluster_nodes.split('\n')
        for cluster_node in cluster:
            if cluster_node == '':
                continue
            node = cluster_node.split(' ')
            if node[1] in nodes:
                continue
            nodes.append(node[1])
        LOGGER.info(f"Parse cluster_nodes success, pid: {self.pid}.")
        return nodes

    def _parse_hosts_cluster_nodes(self, hosts_node):
        LOGGER.info("Begin to parse cluster_nodes")
        pattern = r"(.*[\.|:]\d+)\s+(.*)\s+"
        result = re.findall(pattern, hosts_node)
        host_node = dict(map(lambda x: (x[1], x[0]), result))
        LOGGER.info(f"Parse cluster_nodes success, pid: {self.pid}.")
        return host_node

    def _parse_instance(self, instance_name: str):
        LOGGER.info("Begin to parse instance")
        db2_instance = ""
        instance = instance_name.split("\n")
        LOGGER.info(f"Start to parse instance name: {instance}")
        for instance_name in instance:
            if instance_name == "":
                continue
            split_instance_name = instance_name.split(":")
            if len(split_instance_name) > 1:
                db2_instance = split_instance_name[1].strip()
                break
            split_instance_name = instance_name.split("：")
            if len(split_instance_name) > 1:
                db2_instance = split_instance_name[1].strip()
                break
        LOGGER.info(f"Begin to parse instance success, pid: {self.pid}.")
        return db2_instance
